package processor;


import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 *
 * @authors Charlie Hatcher, Austin Benesh
 *
 */

public class Methods {
	
	public int myRegisters[] = new int[16];
	public Map<String,Method> methodMap;
	public String[] registerNames = new String[]{"%eax","%ecx","%edx","%ebx","%ebp","%esp","%ebp","%esi","%edi"};
	
	public String myMemory[] = new String[4096];
	
	public boolean ZF; //Zero flag
	public boolean SF; //Sign flag
	public boolean OF; //Overflow Flag
	public boolean HF; //Halt flag
	
	public int programCounter;

	/**
	 * Constructor for the methods, this is only used to populate memory
and registers
	 * currently.
	 */
	public Methods(){
		buildMethodMap();
	
		ZF = false;
		SF = false;
		OF = false;
		HF = false;
	}
	
	
	/**
	 * Add value from registers args[1] and args[2], write the value back to
	 * args[1]
	 * @param args
	 * @throws Exception
	 */
	public void addl(String arg) throws Exception{
		setFlags();
		int registerA = Integer.parseInt(arg.substring(0,1),16);
		int registerB = Integer.parseInt(arg.substring(1,2),16);
		int register1value = myRegisters[registerA];
		int register2value = myRegisters[registerB];
		System.out.println("addl " + registerNames[registerA] + "," + registerNames[registerB]);
		int resultvalue = register1value + register2value;
		checkZeroFlag(resultvalue);
		checkSignFlag(resultvalue);
		checkOverFlowFlag(register1value, register2value, resultvalue);
		myRegisters[registerB] =  resultvalue;
		programCounter += 2;
	}


	/**
	 * Subtract value from args[2] from args[1], write the value back to args[1]
	 * @param args
	 * @throws Exception
	 */
	public void subl(String arg) throws Exception{
		setFlags();
		int registerA = Integer.parseInt(arg.substring(0,1),16);
		int registerB = Integer.parseInt(arg.substring(1,2),16);
		System.out.println("subl " + registerNames[registerA] + "," + registerNames[registerB]);
		int register1value = myRegisters[registerA];
		int register2value = myRegisters[registerB];
		int resultvalue = register2value - register1value;
		checkZeroFlag(resultvalue);
		checkSignFlag(resultvalue);
		checkOverFlowFlag(register1value, register2value, resultvalue);
		myRegisters[registerB] = resultvalue;
		programCounter +=2;
	}
	
	
	/**
	 * Bitwise logical and: args[2] = args[1] & args[2]
	 * @param args
	 * @throws Exception
	 */
	public void andl(String arg) throws Exception{
		setFlags();
		int registerA = Integer.parseInt(arg.substring(0,1),16);
		int registerB = Integer.parseInt(arg.substring(1,2),16);
		System.out.println("andl " + registerNames[registerA] + "," + registerNames[registerB]);
		int register1value = myRegisters[registerA];
		int register2value = myRegisters[registerB];
		int resultvalue = register1value & register2value;
		checkZeroFlag(resultvalue);
		checkSignFlag(resultvalue);
		checkOverFlowFlag(register1value, register2value, resultvalue);
		myRegisters[registerB] = resultvalue;
		programCounter += 2;
	}
	

	/**
	 * exclusive or: args[2] = args[1] ^ args[2]
	 * @param args
	 * @throws Exception
	 */
	public void xorl(String arg) throws Exception{
		setFlags();
		int registerA = Integer.parseInt(arg.substring(0,1),16);
		int registerB = Integer.parseInt(arg.substring(1,2),16);
		System.out.println("xorl " + registerNames[registerA] + "," + registerNames[registerB]);
		int register1value = myRegisters[registerA];
		int register2value = myRegisters[registerB];
		int resultvalue = register1value ^ register2value;
		checkZeroFlag(resultvalue);
		checkSignFlag(resultvalue);
		checkOverFlowFlag(register1value, register2value, resultvalue);
		myRegisters[registerB] = resultvalue;
		programCounter += 2;
		}

	/**
	 * register to register move, copies value from args[1] buts it in args[2]
	 * @param args
	 * @throws Exception
	 */
	public void rrmovl(String arg) throws Exception{
		int registerA = Integer.parseInt(arg.substring(0,1),16);
		int registerB = Integer.parseInt(arg.substring(1,2),16);
		System.out.println("rrmovl " + registerNames[registerA] + "," + registerNames[registerB]);
		int register1value = myRegisters[registerA];
		int resultvalue = 0 + register1value;
		myRegisters[registerB] = resultvalue;
		programCounter += 2;
	}
	
	/**
	 * immediate to register move
	 * puts 32 bit value in register rB
	 * @param args
	 * @throws Exception
	 */
	public void irmovl(String arg) throws Exception{
		int registerB = Integer.parseInt(arg.substring(1,2),16);
		long valC = Long.parseLong(reverseBytes(arg.substring(2)),16);
		long checkedValC = checkLong(valC);
		int integerValC = (int)(checkedValC);
		System.out.println("irmovl $" + valC + "," + registerNames[registerB]);
		int resultvalue = 0 + integerValC;
		myRegisters[registerB] = resultvalue;
		programCounter+=6;
	}
	
	/**
	 * register to memory move
	 * copies from args[1] to memory location Mem[args[2]]
	 * @param args
	 * @throws Exception
	 */
	public void rmmovl(String arg) throws Exception{
		int registerA = Integer.parseInt(arg.substring(0,1),16);
		int registerB = Integer.parseInt(arg.substring(1,2),16);
		int register1value = myRegisters[registerA];
		int valC = Integer.parseInt(arg.substring(2));
		System.out.println("rmmovl " + registerNames[registerA] + "," + valC + "(" + registerNames[registerB] +")");
		int valE = valC + registerB;
		myMemory[valE] = reverseBytes(Integer.toHexString(register1value));
		programCounter+=6;
	}
	
	/**
	 * memory to register move
	 * copies contents of Mem[args[2]] to args[1]
	 * @param args
	 */
	public void mrmovl(String arg){
		int registerA = Integer.parseInt(arg.substring(0,1),16);
		int registerB = Integer.parseInt(arg.substring(1,2),16);
		int valC = Integer.parseInt(reverseBytes(arg.substring(2)),16);
		System.out.println("mrmovl " + valC + "(" + registerNames[registerA] + ")," + registerNames[registerB]);
		int valE = myRegisters[registerB]+valC;
		myRegisters[registerA] = Integer.parseInt(reverseBytes(myMemory[valE]),16);
		programCounter+=6;
	}
	
	/**
	 * Performs a rrmovl if the ... is less than or equal to...
	 * @param args
	 * @throws Exception
	 */
	public void cmovle(String args) throws Exception{
		System.out.println("cmovle");
		if((SF^OF)||ZF){
			rrmovl(args);
		}
		else programCounter +=2;
	}
	
	/**
	 * Performs a rrmove if the ... is less than ...
	 * @param args
	 * @throws Exception
	 */
	public void cmovl(String args) throws Exception{
		System.out.println("cmovl");
		if(SF^OF){
			rrmovl(args);
		}
		else programCounter +=2;
	}
	
	/**
	 * Performs a rrmovl if the ... is equal to...
	 * @param args
	 * @throws Exception
	 */
	public void cmove(String args) throws Exception{
		System.out.println("cmove");
		if(ZF){
			rrmovl(args);
		}
		else programCounter+=2;
	}
	
	
	/**
	 * Performs a rrmovl if the ... is not equal to ...
	 * @param args
	 * @throws Exception
	 */
	public void cmovne(String args) throws Exception{
		System.out.println("cmovne");
		if(!ZF){	
			rrmovl(args);
		}
		else programCounter+=2;
	}
	
	/**
	 * Performs a rrmovl if the... is greater than or equal to...
	 * @param args
	 * @throws Exception
	 */
	public void cmovge(String args) throws Exception{
		System.out.println("cmovge");
		if(!(SF^OF)){
			rrmovl(args);
		}
		else programCounter+=2;
	}
	
	/**
	 * Performs a rrmovl if the... is greater than ...
	 * @param args
	 * @throws Exception
	 */
	public void cmovg(String args) throws Exception{
		System.out.println("cmovge");
		if((!(SF^OF))&&!ZF){
			rrmovl(args);
		}
		else programCounter+=2;
	}
	
	/**
	 * unconditional jump to args[1]
	 * @param args
	 */
	public void jmp(String args){
		System.out.println("jmp");
		int dest = Integer.parseInt(args,16);
		programCounter = dest;
	}
	
	/**
	 * jump if previous result was less than or equal to 0 based on
signed arithmetic
	 * @param args
	 */
	public void jle(String args){
		System.out.println("jle");
		if((SF^OF)||ZF){
			int dest = Integer.parseInt(args,16);
			programCounter = dest;
		}
		else
			programCounter += 5;
	}
	
	/**
	 * jump if previous result was less than 0
	 * @param args
	 */
	public void jl(String args){
		System.out.println("jl");
		if(SF^OF){
			int dest = Integer.parseInt(args,16);
			programCounter = dest;
		}
		else
			programCounter += 5;
	}
	
	/**
	 * jump if previous result was equal to 0
	 * @param args
	 */
	public void je(String args){
		System.out.println("je");
		if(ZF){
			int dest = Integer.parseInt(args,16);
			programCounter = dest;
		}
		else
			programCounter += 5;
	}
	
	/**
	 * jump if previous result was not equal to 0
	 * @param args
	 */
	public void jne(String args){
		System.out.println("jne");
		if(!ZF){
			int dest = Integer.parseInt(args.substring(0,2),16);
			programCounter = dest;
		}
		else
			programCounter += 5;
	}
	
	/**
	 * jump if previous result was greater than or equal to 0
	 * @param args
	 */
	public void jge(String args){
		System.out.println("jge");
		if(!(SF^OF)){
			int dest = Integer.parseInt(args,16);
			programCounter = dest;
		}
		else
			programCounter += 5;
	}
	
	/**
	 * jump if previous result was greater than 0
	 * @param args
	 */
	public void jg(String args){
		System.out.println("jg");
		if((!(SF^OF))&&!ZF){
			int dest = Integer.parseInt(args,16);
			programCounter = dest;
		}
		else
			programCounter += 5;
	}
	
	/**
	 * Initiates a subroutine call to args[1]
	 * pushes return address on stack and jumps to args[1]
	 * @param args
	 */
	public void call(String arg){
		
		arg = arg.substring(2) + arg.substring(0,2);
		int valC = Integer.parseInt(arg,16);
		System.out.println("Call " + valC);
		int valB = myRegisters[4];
		int valP = programCounter + 5;
		int valE = valB - 4;
		myMemory[valE] = reverseBytes(Integer.toHexString(valP));
		myRegisters[4] = valE;
		programCounter = valC;
	}
	
	/**
	 * return from subroutine
	 * pops return address on stack and jumps to return address
	 * @param args
	 */
	public void ret(String s){
		System.out.println("ret");
		int valA = myRegisters[4];
		int valB = myRegisters[4];
		int valE = valB + 4;
		int valM = Integer.parseInt(reverseBytes(myMemory[valA]),16);
		myRegisters[4] = valE;
		programCounter = valM;
	}
	/**
	 * push register args[1] on the stack
	 * @param args
	 */
	public void pushl(String arg){
		System.out.println("pushl %ebp");
		int reg = Integer.parseInt(arg.substring(0, 1),16);
		int valA = myRegisters[reg];	//value of reg to
		int valB = myRegisters[4];		//val of stack pointer
		int valE = valB - 4;			//dec stack pointer
		myMemory[valE] = Integer.toHexString(valA);
		myRegisters[4] = valE;
		programCounter += 2;
	}
	
	/**
	 * pop from stack to args[1]
	 * @param args
	 */
	public void popl(String arg){
		System.out.println("popl %ebp");
		int rA = Integer.parseInt(arg.substring(0,1),16);
		int valA = myRegisters[4];
		int valB = myRegisters[4];
		int valE = valB + 4;
		int valM = Integer.parseInt(myMemory[valA],16);
		myRegisters[4] = valE;
		myRegisters[rA] = valM;
		programCounter += 2;
	}
	
	/**
	 * No operation, does nothing
	 * @param args
	 */
	public void nop(String args){
		System.out.println("nop");
		programCounter++;
	}
	
	/**
	 * Stops the simulator
	 * @param args
	 */
	public void halt(String args){
		System.out.println("halt");
		HF = true;
	}
	
	
	/**
	 * Checks to see if the overflow flag needs to be set
	 * @param register1value
	 * @param register2value
	 * @param resultvalue
	 */
	private void checkOverFlowFlag(int register1value, int register2value,
			int resultvalue) {
		if(((register1value<0)==(register2value<0))&&((resultvalue<0)!=(register1value<0))){
			OF = true;
		}
	}
	

	/**
	 * Check to see if the zero flag needs to be set
	 * @param i
	 */
	private void checkZeroFlag(int i) {
		if(i==0){
			ZF = true;
		}
	}

	/**
	 *Sets all the flags to false initially.
	 */
	private void setFlags(){
		ZF=false;
		SF = false;
		OF = false;
	}

	/**
	 * Check to see if the sign flag needs to be set
	 * @param i
	 */
	private void checkSignFlag(int i) {
		if(i<0){
			SF = true;
		}
	}
	public String reverseBytes(String command)
	{
		String reversedByteCommand = command;
		if(command.length() > 2)
		{
			reversedByteCommand = "";
			for(int i=command.length(); i>1; i-=2)
				reversedByteCommand = reversedByteCommand + command.substring(i-2,i);
		}
		return reversedByteCommand;
	}
	private long checkLong(Long UNSIGNED_VAR){
		if(UNSIGNED_VAR > Integer.MAX_VALUE){
			UNSIGNED_VAR = (2L * Integer.MAX_VALUE) - UNSIGNED_VAR;
			return UNSIGNED_VAR;
		}
		else return UNSIGNED_VAR;
	}
	public void buildMethodMap()
	{
			methodMap = new HashMap<String,Method>();
			for(Method m : this.getClass().getDeclaredMethods())
				methodMap.put(m.getName(),m);
	}
	public void callMethod(String opcode,String command)
	{
			try {
				Method m = methodMap.get(opcode);
				m.invoke(this,command);
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			}
		
	}
}
